// Copyright (c) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::XmlError;
use std::{
    io::{Bytes, Read},
    str::Chars,
};

/// Reader for reading UTF-8 characters.
pub(crate) trait Utf8Reader {
    /// Get the next UTF-8 character and move the cursor to the next character.
    fn next(&mut self) -> Result<Option<char>, XmlError>;

    /// Peek the next UTF-8 character, the cursor does not move.
    fn peek(&mut self) -> Result<Option<char>, XmlError>;

    /// Use with Peek(), discard the character just peeked.
    fn discard(&mut self);

    /// Get the current cursor position.
    fn position(&self) -> Position;

    /// Use with bytes_end(), set the starting position of the intercepted
    /// string slice.
    fn bytes_start(&mut self);

    /// Use with bytes_start(), set the end position of the intercepted string
    /// slice.
    fn bytes_end(&mut self) -> &[u8];
}

/// Cursor Position.
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Position {
    pub(crate) line: usize,
    pub(crate) offset: usize,
}

/// Reader for processing `&str`.
pub(crate) struct StrReader<'a> {
    chars: Chars<'a>,   // Iterator for &str.
    str: &'a str,       // The original slice.
    buf: Option<char>,  // Buffer for storing peeked character.
    start_flag: bool,   // Slice marker.
    bytes_start: usize, // The starting position of the slice
    bytes_index: usize, // The current position of the cursor
}

impl<'a> StrReader<'a> {
    /// Create a `StrReader`.
    pub(crate) fn new(str: &'a str) -> Self {
        Self {
            chars: str.chars(),
            str,
            buf: None,
            start_flag: false,
            bytes_start: 0,
            bytes_index: 0,
        }
    }
}

impl<'a> Utf8Reader for StrReader<'a> {
    fn next(&mut self) -> Result<Option<char>, XmlError> {
        if let Some(ch) = self.buf {
            self.bytes_index += ch.len_utf8();
            return Ok(self.buf.take());
        }
        if let Some(ch) = self.chars.next() {
            self.bytes_index += ch.len_utf8();
            return Ok(Some(ch));
        }
        Ok(None)
    }

    fn peek(&mut self) -> Result<Option<char>, XmlError> {
        if self.buf.is_some() {
            return Ok(self.buf);
        }
        let ch = self.chars.next();
        self.buf = ch;
        Ok(ch)
    }

    fn discard(&mut self) {
        if let Some(ch) = self.buf.take() {
            self.bytes_index += ch.len_utf8();
        }
    }

    fn position(&self) -> Position {
        let mut line = 1;
        let mut column = 1;
        for ch in self.str.chars() {
            if ch == '\n' {
                line += 1;
                column = 1;
            }
            column += 1;
        }
        Position {
            line,
            offset: column,
        }
    }

    fn bytes_start(&mut self) {
        self.start_flag = true;
        self.bytes_start = self.bytes_index;
    }

    fn bytes_end(&mut self) -> &[u8] {
        self.start_flag = false;
        self.str[self.bytes_start..self.bytes_index].as_bytes()
    }
}

/// Reader to work with `std::io::Read` objects.
pub(crate) struct IoReader<T: Read> {
    bytes: Bytes<T>,
    buf: Option<char>,   // Buffer for storing peeked character.
    char_bytes: [u8; 4], // Buffer for storing peeked bytes.
    char_len: usize,     // Length of char_bytes' internal bytes.
    start_flag: bool,    // Slice marker.
    vec: Vec<u8>,        // Buffer for storing a slice.
    line: usize,         // Cursor's line.
    column: usize,       // Cursor's column.
}

impl<T: Read> IoReader<T> {
    pub(crate) fn new(io: T) -> Self {
        Self {
            bytes: io.bytes(),
            buf: None,
            char_bytes: [0u8; 4], // UTF-8 character requires up to 4 bytes to represent.
            char_len: 0,
            start_flag: false,
            vec: Vec::new(),
            line: 1,   // Start from line 1.
            column: 1, // Start from column 1.
        }
    }

    pub(crate) fn read_byte(&mut self) -> Result<Option<u8>, XmlError> {
        let byte = self.bytes.next();
        match byte {
            None => Ok(None),
            Some(Ok(x)) => Ok(Some(x)),
            Some(Err(e)) => Err(e.into()),
        }
    }

    pub(crate) fn read_char(&mut self) -> Result<Option<char>, XmlError> {
        const CONT_MASK: u8 = 0b0011_1111;
        const fn utf8_first_byte(byte: u8, width: u32) -> u32 {
            (byte & (0x7F >> width)) as u32
        }

        const fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 {
            (ch << 6) | (byte & CONT_MASK) as u32
        }

        self.char_len = 0;

        let x = match self.read_byte()? {
            Some(x) => x,
            None => return Ok(None),
        };
        self.char_bytes[self.char_len] = x;
        self.char_len += 1;

        // ASCII characters.
        let ch = if x < 128 {
            x as u32
        } else {
            let init = utf8_first_byte(x, 2);

            let y = match self.read_byte()? {
                Some(x) => x,
                None => return Ok(None),
            };
            self.char_bytes[self.char_len] = y;
            self.char_len += 1;

            let mut ch = utf8_acc_cont_byte(init, y);

            // 0b1110_0000, we can use this MASK to determine whether the
            // character is of length 3.
            if x >= 0xE0 {
                let z = match self.read_byte()? {
                    Some(x) => x,
                    None => return Ok(None),
                };
                self.char_bytes[self.char_len] = z;
                self.char_len += 1;

                let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, z);
                ch = init << 12 | y_z;

                // 0b1111_0000, we can use this MASK to determine whether the
                // character is of length 4.
                if x >= 0xF0 {
                    let w = match self.read_byte()? {
                        Some(x) => x,
                        None => return Ok(None),
                    };
                    self.char_bytes[self.char_len] = w;
                    self.char_len += 1;
                    ch = (init & 7) << 18 | utf8_acc_cont_byte(y_z, w);
                }
            }
            ch
        };
        unsafe { Ok(Some(char::from_u32_unchecked(ch))) }
    }
}

impl<T: Read> Utf8Reader for IoReader<T> {
    fn next(&mut self) -> Result<Option<char>, XmlError> {
        let ch = if self.buf.is_some() {
            self.buf.take()
        } else {
            self.read_char()?
        };
        if let Some(ch) = ch {
            if ch == '\n' {
                self.line += 1;
                self.column = 1;
            } else {
                self.column += 1;
            }
            if self.start_flag {
                self.vec
                    .extend_from_slice(&self.char_bytes[0..self.char_len]);
            }
        }
        Ok(ch)
    }

    fn peek(&mut self) -> Result<Option<char>, XmlError> {
        if self.buf.is_some() {
            return Ok(self.buf);
        }
        let ch = self.read_char()?;
        self.buf = ch;
        Ok(ch)
    }

    fn discard(&mut self) {
        let ch = self.buf.take();
        if let Some(ch) = ch {
            if ch == '\n' {
                self.line += 1;
                self.column = 1;
            } else {
                self.column += 1;
            }
            if self.start_flag {
                self.vec
                    .extend_from_slice(&self.char_bytes[0..self.char_len]);
            }
        }
    }

    fn position(&self) -> Position {
        Position {
            line: self.line,
            offset: self.column,
        }
    }

    fn bytes_start(&mut self) {
        self.start_flag = true;
        self.vec.clear();
    }

    fn bytes_end(&mut self) -> &[u8] {
        self.start_flag = false;
        self.vec.as_slice()
    }
}

#[cfg(test)]
mod ut_reader {
    use std::{cmp::min, io::Read};

    use crate::reader::{IoReader, Position, StrReader, Utf8Reader};

    /*
     * @title  UT test for next \n count
     * @design Condition Coverage
     * @precon NA
     * @brief  1. Construct IO read
     *         2. Call next to read /n.
     *         3. Check the result.
     * @expect 1. true。
     * @auto   yes
     */
    #[test]
    fn ut_io_reader_multi_lines() {
        struct TestIo {
            vec: Vec<u8>,
            index: usize,
        }
        impl Read for TestIo {
            fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
                let min = min(buf.len(), self.vec.len() - self.index);
                if min == 0 {
                    return Ok(0);
                }
                buf.copy_from_slice(&self.vec[self.index..self.index + min]);
                self.index += min;
                Ok(min)
            }
        }
        let vec = "GR\n".as_bytes().to_vec();
        let test_io = TestIo { vec, index: 0 };
        let mut io_reader = IoReader::<TestIo>::new(test_io);
        assert_eq!(io_reader.peek(), Ok(Some('G')));

        io_reader.bytes_start();
        io_reader.discard();
        assert_eq!(io_reader.next(), Ok(Some('R')));

        io_reader.discard();
        assert_eq!(io_reader.next(), Ok(Some('\n')));
        io_reader.discard();
    }

    /*
     * @title  UT test for discard \n count
     * @design Condition Coverage
     * @precon NA
     * @brief  1. Construct IO read
     *         2. Call peek to read /n.
     *         3. Check the result.
     * @expect 1. true。
     * @auto   yes
     */
    #[test]
    fn ut_io_reader_discard_lines() {
        struct TestIo {
            vec: Vec<u8>,
            index: usize,
        }
        impl Read for TestIo {
            fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
                let min = min(buf.len(), self.vec.len() - self.index);
                if min == 0 {
                    return Ok(0);
                }
                buf.copy_from_slice(&self.vec[self.index..self.index + min]);
                self.index += min;
                Ok(min)
            }
        }
        let vec = "GR\n".as_bytes().to_vec();
        let test_io = TestIo { vec, index: 0 };
        let mut io_reader = IoReader::<TestIo>::new(test_io);
        assert_eq!(io_reader.peek(), Ok(Some('G')));

        io_reader.bytes_start();
        io_reader.discard();
        assert_eq!(io_reader.next(), Ok(Some('R')));

        io_reader.discard();
        assert_eq!(io_reader.peek(), Ok(Some('\n')));
        io_reader.discard();
    }

    /*
     * @title  UT test for position \n count
     * @design Condition Coverage
     * @precon NA
     * @brief  1. Construct Str read
     *         2. Call next to read /n.
     *         3. Check the position result.
     * @expect 1. true。
     * @auto   yes
     */
    #[test]
    fn ut_str_reader_position() {
        let str = "GR\n";
        let mut str_reader = StrReader::new(str);
        assert_eq!(str_reader.peek(), Ok(Some('G')));

        str_reader.discard();
        assert_eq!(str_reader.next(), Ok(Some('R')));

        str_reader.discard();
        assert_eq!(str_reader.next(), Ok(Some('\n')));
        assert_eq!(str_reader.position(), Position { line: 2, offset: 2 });
    }

    /// UT test case for `StrReader`.
    ///
    /// # Brief
    /// 1. Create a `StrReader`.
    /// 2. Call a series of methods of `StrReader` and check the result.
    #[test]
    fn ut_str_reader() {
        let str = "GRüßE, JüRGEN ❤";
        let mut str_reader = StrReader::new(str);

        assert_eq!(str_reader.peek(), Ok(Some('G')));
        assert_eq!(str_reader.peek(), Ok(Some('G')));
        assert_eq!(str_reader.peek(), Ok(Some('G')));

        str_reader.discard();
        assert_eq!(str_reader.next(), Ok(Some('R')));
        str_reader.bytes_start();
        assert_eq!(str_reader.next(), Ok(Some('ü')));
        assert_eq!(str_reader.next(), Ok(Some('ß')));
        assert_eq!(str_reader.next(), Ok(Some('E')));
        assert_eq!(str_reader.next(), Ok(Some(',')));
        assert_eq!(str_reader.next(), Ok(Some(' ')));
        assert_eq!(str_reader.bytes_end(), "üßE, ".as_bytes());

        str_reader.bytes_start();
        assert_eq!(str_reader.next(), Ok(Some('J')));
        assert_eq!(str_reader.next(), Ok(Some('ü')));
        assert_eq!(str_reader.next(), Ok(Some('R')));
        assert_eq!(str_reader.next(), Ok(Some('G')));
        assert_eq!(str_reader.next(), Ok(Some('E')));
        assert_eq!(str_reader.next(), Ok(Some('N')));
        assert_eq!(str_reader.next(), Ok(Some(' ')));
        assert_eq!(str_reader.next(), Ok(Some('❤')));
        assert_eq!(str_reader.next(), Ok(None));
        assert_eq!(str_reader.next(), Ok(None));

        assert_eq!(
            str_reader.position(),
            Position {
                line: 1,
                offset: 16
            }
        );
        assert_eq!(str_reader.bytes_end(), "JüRGEN ❤".as_bytes());
    }

    /// UT test case for `Reader`.
    ///
    /// # Brief
    /// 1. Create a `Reader`.
    /// 2. Call a series of methods of `Reader` and check the result.
    #[test]
    fn ut_io_reader() {
        struct TestIo {
            vec: Vec<u8>,
            index: usize,
        }

        impl Read for TestIo {
            fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
                let min = min(buf.len(), self.vec.len() - self.index);
                if min == 0 {
                    return Ok(0);
                }
                buf.copy_from_slice(&self.vec[self.index..self.index + min]);
                self.index += min;
                Ok(min)
            }
        }

        let vec = "GRüßE, JüRGEN ❤".as_bytes().to_vec();
        let test_io = TestIo { vec, index: 0 };
        let mut io_reader = IoReader::<TestIo>::new(test_io);

        assert_eq!(io_reader.peek(), Ok(Some('G')));
        assert_eq!(io_reader.peek(), Ok(Some('G')));
        assert_eq!(io_reader.peek(), Ok(Some('G')));

        io_reader.discard();
        assert_eq!(io_reader.next(), Ok(Some('R')));
        io_reader.bytes_start();
        assert_eq!(io_reader.next(), Ok(Some('ü')));
        assert_eq!(io_reader.next(), Ok(Some('ß')));
        assert_eq!(io_reader.next(), Ok(Some('E')));
        assert_eq!(io_reader.next(), Ok(Some(',')));
        assert_eq!(io_reader.next(), Ok(Some(' ')));
        assert_eq!(io_reader.bytes_end(), "üßE, ".as_bytes());

        io_reader.bytes_start();
        assert_eq!(io_reader.next(), Ok(Some('J')));
        assert_eq!(io_reader.next(), Ok(Some('ü')));
        assert_eq!(io_reader.next(), Ok(Some('R')));
        assert_eq!(io_reader.next(), Ok(Some('G')));
        assert_eq!(io_reader.next(), Ok(Some('E')));
        assert_eq!(io_reader.next(), Ok(Some('N')));
        assert_eq!(io_reader.next(), Ok(Some(' ')));
        assert_eq!(io_reader.next(), Ok(Some('❤')));
        assert_eq!(io_reader.next(), Ok(None));
        assert_eq!(io_reader.next(), Ok(None));

        assert_eq!(
            io_reader.position(),
            Position {
                line: 1,
                offset: 16
            }
        );
        assert_eq!(io_reader.bytes_end(), "JüRGEN ❤".as_bytes());
    }
}
